package com.lu.component.http.proxy.clients;

import cn.hutool.core.convert.BasicType;
import cn.hutool.core.io.resource.BytesResource;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.net.url.UrlQuery;
import cn.hutool.core.util.*;
import cn.hutool.http.ContentType;
import cn.hutool.http.Header;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONUtil;
import com.lu.component.http.proxy.config.RpcApplicationContext;
import com.lu.component.http.proxy.support.flow.RpcFlowControlStandalone;
import com.lu.component.rpc.utils.JavassistUtil;
import com.lu.component.rpc.annotations.*;
import com.lu.component.rpc.model.domain.request.RpcBytesMultipartFile;
import com.lu.component.rpc.model.domain.response.RpcResponse;
import com.lu.component.rpc.model.enums.RpcContentTypeEnum;
import com.lu.component.rpc.model.enums.RpcFlowControlEnum;
import com.lu.component.rpc.model.enums.RpcMethodEnum;
import com.lu.component.rpc.model.exceptions.RpcExeception;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.*;

/**
 * rpc代理实现
 *
 * @author: zhanglu
 * @date: 2023/7/6 20:56
 * @modify: 修改人信息, 修改内容 2023/7/6 20:56
 */
public class RpcInvocationHandler implements InvocationHandler {

    private static final Logger log = LoggerFactory.getLogger(RpcInvocationHandler.class);

    /**
     * rpc接口类
     */
    private Class<?> type;

    public RpcInvocationHandler(Class<?> type) {
        this.type = type;
    }

    @Override
    public Object invoke(Object object, Method method, Object[] args) throws Throwable {
        Class<?>[] parameterTypes = method.getParameterTypes();
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        Class<?> returnType = method.getReturnType();
        RpcClient rpcClient = method.getDeclaringClass().getAnnotation(RpcClient.class);
        RpcApiRequest rpcApiRequest = method.getAnnotation(RpcApiRequest.class);
        //流控
        if(Objects.equals(RpcFlowControlEnum.STAND_ALONE, rpcApiRequest.limit())){
            return new RpcFlowControlStandalone(rpcApiRequest).execute(ObjectUtil.isEmpty(rpcApiRequest.limitKey())
                            ? method.getDeclaringClass().getName()  + StrUtil.UNDERLINE + method.getName() : rpcApiRequest.limitKey(),
                            () -> rpcExecute(method, args, parameterTypes, parameterAnnotations, returnType, rpcClient, rpcApiRequest),
                            () -> fallback(method, args, new RpcResponse(false, "服务繁忙"))
            );
        } else if(Objects.equals(RpcFlowControlEnum.SERVER_CLUSTER, rpcApiRequest.limit())) {
            return fallback(method, args, new RpcResponse(false, "暂不支持集群流控"));
        } else {
            return rpcExecute(method, args, parameterTypes, parameterAnnotations, returnType, rpcClient, rpcApiRequest);
        }
    }

    /**
     * 执行rpc请求
     * @param method
     * @param args
     * @param parameterTypes
     * @param parameterAnnotations
     * @param returnType
     * @param rpcClient
     * @param rpcApiRequest
     * @return
     */
    public Object rpcExecute(Method method, Object[] args, Class<?>[] parameterTypes,
                             Annotation[][] parameterAnnotations, Class<?> returnType,
                             RpcClient rpcClient, RpcApiRequest rpcApiRequest){
        Long connectionTimeout = RpcApplicationContext.TIMEOUT;
        Long readTimeout = RpcApplicationContext.TIMEOUT;
        cn.hutool.http.Method requestMethod = cn.hutool.http.Method.POST;
        //执行rpc调用
        HttpResponse execute = null;
        try {
            if(ObjectUtil.isNotEmpty(rpcClient.connectionTimeout()) && !Objects.equals(-1, rpcClient.connectionTimeout())){
                connectionTimeout = rpcClient.connectionTimeout();
            }
            if(ObjectUtil.isNotEmpty(rpcApiRequest.connectionTimeout()) && !Objects.equals(-1, rpcApiRequest.connectionTimeout())){
                connectionTimeout = rpcApiRequest.connectionTimeout();
            }
            if(ObjectUtil.isNotEmpty(rpcClient.readTimeout()) && !Objects.equals(-1, rpcClient.readTimeout())){
                readTimeout = rpcClient.readTimeout();
            }
            if(ObjectUtil.isNotEmpty(rpcApiRequest.readTimeout()) && !Objects.equals(-1, rpcApiRequest.readTimeout())){
                readTimeout = rpcApiRequest.readTimeout();
            }
            if(Objects.equals(RpcMethodEnum.GET, rpcApiRequest.method())){
                requestMethod = cn.hutool.http.Method.GET;
            }
            String[] methodParams = JavassistUtil.getMethodParams(method);
            UrlBuilder urlBuilder = UrlBuilder.of(rpcClient.host(), CharsetUtil.CHARSET_UTF_8)
                    .setQuery(new UrlQuery())
                    .addPath(rpcClient.path()).addPath(rpcApiRequest.value());
            HttpRequest request = new HttpRequest(urlBuilder).method(requestMethod)
                    .setConnectionTimeout(connectionTimeout.intValue()).setReadTimeout(readTimeout.intValue());
            if(!Objects.equals(RpcContentTypeEnum.AUTO, rpcApiRequest.contentType())){
                request.contentType(rpcApiRequest.contentType().getValue());
            }
            Map<String, Object> body = new LinkedHashMap<>();
            for (int i = 0; i < parameterTypes.length; i++) {
                Annotation[] parameterAnnotation = parameterAnnotations[i];
                if (args[i] == null) {
                    continue;
                }
                if (Objects.equals(RpcRequestParam.class, parameterAnnotation[0].annotationType())) {
                    if (args[i] instanceof RpcBytesMultipartFile) {
                        RpcBytesMultipartFile multipartFile = (RpcBytesMultipartFile) args[i];
                        request.form(methodParams[i], new BytesResource(multipartFile.getBytes(), multipartFile.getName()));
                    } else {
                        urlBuilder.addQuery(methodParams[i], args[i].toString());
                    }
                } else if (Objects.equals(RpcRequestBody.class, parameterAnnotation[0].annotationType())) {
                    request.header(Header.CONTENT_TYPE, ContentType.JSON.getValue());
                    if (args[i] instanceof String || ClassUtil.isBasicType(returnType)) {
                        request.body(args[i].toString());
                    } else {
                        body.put(methodParams[i], args[i]);
                    }
                } else if (Objects.equals(RpcPathVariable.class, parameterAnnotation[0].annotationType())) {
                    List<String> segments = urlBuilder.getPath().getSegments();
                    String segment = segments.get(urlBuilder.getPath().getSegments().size() - 1);
                    segments.remove(urlBuilder.getPath().getSegments().size() - 1);
                    segments.add(segment.replace("{" + methodParams[i] + "}", args[i].toString()));
                } else if (Objects.equals(RpcRequestHeader.class, parameterAnnotation[0].annotationType())) {
                    request.header(methodParams[i], args[i].toString());
                }
            }
            if (ObjectUtil.isNotEmpty(body)) {
                request.body(JSONUtil.toJsonStr(body));
            }
            log.debug("【http请求执行开始】，方法：{}({})", method.getDeclaringClass().getName() + "#" + method.getName(), StrUtil.join(",", methodParams));
            execute = request.execute();
            log.debug("【http请求执行结束】，方法：{}({})", method.getDeclaringClass().getName() + "#" + method.getName(), StrUtil.join(",", methodParams));
            RpcResponse response = new RpcResponse(execute.getStatus(), execute.isOk());
            HttpResponse finalExecute = execute;
            Assert.isTrue(response.isOk(), () -> new RpcExeception(finalExecute.body(), response));
            //结果处理
            return returnProcess(returnType, execute);
        } catch (RpcExeception e) {
            return fallback(method, args, e.getResponse());
        } catch (Exception e) {
            return fallback(method, args, new RpcResponse(false, e.getMessage()));
        } finally {
            if (ObjectUtil.isNotEmpty(execute)) {
                execute.close();
            }
        }
    }

    /**
     * 返回结果处理
     * @param returnType    返回类型
     * @param execute       响应
     * @return              业务结果
     */
    private Object returnProcess(Class<?> returnType, HttpResponse execute){
        Object returnObj = null;
        if(Objects.equals(Void.class, returnType)
                || Objects.equals(void.class, returnType)){
            return returnObj;
        }
        if (Objects.equals(String.class, returnType)) {
            returnObj = execute.body();
        } else if (ClassUtil.isBasicType(returnType)) {
            returnObj = ReflectUtil.newInstance(BasicType.wrap(returnType), execute.body());
        } else if (returnType.isArray()) {
            //数组
            if(Objects.equals(Byte[].class, returnType) || Objects.equals(byte[].class, returnType)){
                returnObj = execute.bodyBytes();
            }else {
                //数组类型
                Class<?> componentType = returnType.getComponentType();
                //数组转list
                Collection objects = JSONUtil.toList(execute.body(), componentType);
                //list转数组
                returnObj = ArrayUtil.toArray(objects, componentType);
            }
        } else {
            returnObj = JSONUtil.toBean(execute.body(), returnType, true);
        }
        return returnObj;
    }

    /**
     * 服务降级
     * @param method    方法
     * @param args      参数
     * @param response  请求响应结果
     * @return
     */
    public Object fallback(Method method, Object[] args, RpcResponse response) {
        try {
            //服务熔断与降级处理
            Class<?>[] parameterTypes = method.getParameterTypes();
            RpcClient rpcClient = method.getDeclaringClass().getAnnotation(RpcClient.class);
            Class<?> fallbackClass = rpcClient.fallback();
            Object fallback = fallbackClass.newInstance();
            RpcApplicationContext.RPC_RESPONSE_THREAD_LOCAL.set(response);
            return fallbackClass.getMethod(method.getName(), parameterTypes).invoke(fallback, args);
        } catch (Exception e) {
            throw new RpcExeception(e.getMessage(), response);
        } finally {
            RpcApplicationContext.RPC_RESPONSE_THREAD_LOCAL.remove();
        }
    }

}
